#include "InfoWnd.h"
#include <thread>
#include <mutex>
#include <future>
#include <iostream>
#include <sstream>
#include <cassert>
#include "SleepableCoroutine.h"
#include "resource.h"
using namespace std;
using namespace CggUtil;

#define _COR_END COR_END(m_timer);

enum {
    UM_UPDATE_INFO = WM_USER + 10,
    UM_BLINK,
};

BEGIN_MSG_MAP(InfoWnd);
    ON_MSG(WM_CREATE, onWndInit);
    ON_MSG(WM_CTLCOLORSTATIC, onCtrlColor);
    ON_MSG(WM_PAINT, onPaint);
    ON_MSG(WM_DESTROY, onDestroy);
    ON_MSG(UM_UPDATE_INFO, onUpdateInfo);
END_MSG_MAP;

std::map<HWND, InfoWnd*> InfoWnd::m_hwnd2Obj;
thread InfoWnd::m_msgThread;
InfoWnd* InfoWnd::m_instance = NULL;

std::string loadResString(int id) {
    static char buf[256];
    memset(buf, 0, sizeof(buf));
    LoadString(NULL, id, buf, 256);
    string str = buf;
    return str;
}

InfoWnd::InfoWnd() :
m_blink(true) {
}

InfoWnd::~InfoWnd() {
    m_msgThread.detach();
}

bool InfoWnd::create(HINSTANCE hinst) {
    promise<bool> isInitSucceeded;
    future<bool> result = isInitSucceeded.get_future();

    // ui must be created & handled in a stand alone thread
    m_msgThread = thread([&]() {
        assert(NULL == m_instance);
        InfoWnd* p = new InfoWnd;
        if (p && !p->init(hinst)) {
            delete p;
            p = NULL;
        }

        // notify main thread
        m_instance = p;
        isInitSucceeded.set_value(p != NULL);

        // then start message pumping
        if (p) {
            p->wndMsgPump();
        }
    });

    // result will be got when isInitSucceeded.set_value is called 
    return result.get();
}

bool InfoWnd::init(HINSTANCE hinst) {
    // create wnd
    if (!(m_hwnd = createWnd(hinst))) {
        return false;        
    }

    m_hwnd2Obj.insert(make_pair(m_hwnd, this));

    // set info text's font
    HWND infText = GetDlgItem(m_hwnd, IDC_TXT_INFO);    
    LOGFONT fontInf;
    GetObject(GetStockObject(DEFAULT_GUI_FONT), sizeof(LOGFONT), &fontInf);
    strcpy_s(fontInf.lfFaceName, loadResString(IDS_INFO_FONT).c_str());
    fontInf.lfWidth *= 1.3f;
    fontInf.lfHeight *= 1.3f;
    HFONT font = CreateFontIndirect(&fontInf);
    SendMessage(infText, WM_SETFONT, (WPARAM)font, (LPARAM)TRUE);
    SetTextColor(GetDC(infText), RGB(255, 0, 0));

    return true;
}

HWND InfoWnd::createWnd(HINSTANCE hinst) {
    HWND hwnd = CreateDialog(hinst, MAKEINTRESOURCE(IDD_MSGBOX), 0, (DLGPROC)wndMsgProc);
    if (hwnd != NULL) {
        SetLayeredWindowAttributes(hwnd, 0, 192, LWA_ALPHA);
    }
    return hwnd;
}

void InfoWnd::wndMsgPump() {
    clock_t start;
    MSG msg;
    memset(&msg, 0, sizeof(msg));
    const int INTERV = 64;

    while (WM_QUIT != msg.message) {
        start = clock();
        if (PeekMessage(&msg, m_hwnd, 0, 0, PM_REMOVE)) {
            TranslateMessage(&msg);
            DispatchMessage(&msg);
        }

        // update timer to run timer task
        m_timer.update();
        clock_t time = clock() - start;
        if (time < INTERV) {
            this_thread::sleep_for(chrono::milliseconds(INTERV - time));
        } else {
            cout << "WARNING: wnd msg lasts with long time (ms) " << time << endl;
        }
    } // end while
}

LRESULT CALLBACK InfoWnd::wndMsgProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) {
    auto iter = m_hwnd2Obj.find(hwnd);    
    if (iter != m_hwnd2Obj.end()) {
        auto obj = iter->second;
        return EVENT_HANDLER.call(obj, message, wParam, lParam);
    }
    return FALSE;
}

LRESULT InfoWnd::onWndInit(WPARAM, LPARAM) {

    return TRUE;
}

LRESULT InfoWnd::onCtrlColor(WPARAM wparam, LPARAM) {
    SetBkMode((HDC)wparam, TRANSPARENT);
    return (BOOL)((HBRUSH)GetStockObject(WHITE_BRUSH));
}

LRESULT InfoWnd::onPaint(WPARAM, LPARAM) {
    PAINTSTRUCT paint;
    HDC dc = BeginPaint(m_hwnd, &paint);
    
    // line
    HPEN pen = CreatePen(PS_SOLID, 2, RGB(115, 115, 115));
    SelectObject(dc, pen);

    // fill
    HBRUSH brush = (HBRUSH)GetStockObject(WHITE_BRUSH);
    SelectObject(dc, brush);

    // bg rect
    RECT wndRect;
    GetClientRect(m_hwnd, &wndRect);
    Rectangle(dc, 1, 1, wndRect.right, wndRect.bottom);

    // end draw
    DeleteObject(pen);
	EndPaint(m_hwnd, &paint);
    return TRUE;
}

LRESULT InfoWnd::onDestroy(WPARAM, LPARAM) {
    cout << "--------onDestroy" << endl;

    // delete this;
    // m_instance = NULL;
    return FALSE;
}

LRESULT InfoWnd::onUpdateInfo(WPARAM wParam, LPARAM) {
    assert(wParam != 0);
    bool interrupt = (IsWindowVisible(m_hwnd) == TRUE);

    // update text
    const char* text = (const char*)wParam;
    ShowWindow(m_hwnd, TRUE);
    SetDlgItemText(m_hwnd, IDC_TXT_INFO, text);

COR_BEGIN
    // count from 3 to 1, then hide   
    ostringstream oss;
    for (int i = 3; i > 0 && !interrupt; --i) {
        oss.str("");
        oss << i;
        SetDlgItemText(m_hwnd, IDC_SEC, oss.str().c_str());
        COR_SLEEP(1000);
    }
    if (!interrupt) {
        ShowWindow(m_hwnd, FALSE);
    }
_COR_END

    return TRUE;
}

void InfoWnd::showText(const std::string& text) {
    static mutex lock;
    static string textBuf;
    unique_lock<mutex> _(lock);

    textBuf = text;
    ::PostMessage(m_hwnd, UM_UPDATE_INFO, (WPARAM)textBuf.c_str(), 0);
}

void InfoWnd::blink() {

}